'''
Created on Mar 20, 2014

@author: Eric
'''

from . import skeleton

#######

class BadStructureException(Exception):
    pass

#######

def _member_ref_checker(node):
    if isinstance(node, skeleton.MemberRefExprNode):
        current = node.parent
        while current:
            if isinstance(current, skeleton.PageDeclNode):
                for member in current.members:
                    if member.name == node.name:
                        return
            current = current.parent
        msg = "'%s' member reference is undefined @ %s"
        raise BadStructureException(msg % (node.name, node.location()))

#######

class Accumulator(object):
    
    def __init__(self, function):
        self._function = function
        self.mapping = {}
    
    def default(self):
        raise NotImplemented()
    
    def merge(self, left, right):
        raise NotImplemented()
    
    def __call__(self, node):
        result = self._function(node) or self.default()
        for (_, y) in node.children():
            if isinstance(y, list):
                for t in y:
                    result = self.merge(result, self.mapping[t])
            elif isinstance(y, skeleton.Node):
                result = self.merge(result, self.mapping[y])
        self.mapping[node] = result

#######

class CountAccumulator(Accumulator):
    
    def default(self):
        return 0
    
    def merge(self, left, right):
        return left + right

#######

class SetAccumulator(Accumulator):
    
    def default(self):
        return set()
    
    def merge(self, left, right):
        return left | right

#######

class SetVarsMappingCollector(object):
    
    def __init__(self):
        self.mapping = {}
    
    def _set_variables(self, node):
        if isinstance(node, skeleton.AssignStmtNode):
            return set([node.name])
    
    def __call__(self, node):
        if isinstance(node, skeleton.DynamicMemberNode):
            accu = SetAccumulator(self._set_variables)
            node.bottom_up_walk(accu)
            self.mapping.update(accu.mapping)

#######

class _VariableRefChecker(object):
    
    def __init__(self, mapping):
        self._mapping = mapping
    
    def __call__(self, node):
        if isinstance(node, skeleton.VariableRefExprNode):
            last_stmt = None
            current = node.parent
            while current:
                if isinstance(current, skeleton.CodeNode):
                    assert last_stmt in current.stmts
                    for stmt in current.stmts:
                        if stmt == last_stmt:
                            break
                        else:
                            if node.name in self._mapping[stmt]:
                                return
                elif isinstance(current, skeleton.StatementNode):
                    last_stmt = current
                else:
                    last_stmt = None
                current = current.parent
            msg = "'%s' variable reference is undefined @ %s"
            raise BadStructureException(msg % (node.name, node.location()))

#######

class Meta(object):
    
    def __init__(self, file_node):
        self.file_node = file_node
        file_node.bottom_up_walk(_member_ref_checker)
        
        coll = SetVarsMappingCollector()
        file_node.bottom_up_walk(coll)
        self.set_vars_mapping = coll.mapping

        file_node.bottom_up_walk(_VariableRefChecker(self.set_vars_mapping))

